Skip to content

feat(smart-sessions): experimental spend-session abstraction + settlement-layer policy encoding (RHI-6242) - #807

Open
highskore wants to merge 5 commits into
feat/rhi-5798-sdk-onetimefrom
feature/rhi-6242
Open

feat(smart-sessions): experimental spend-session abstraction + settlement-layer policy encoding (RHI-6242)#807
highskore wants to merge 5 commits into
feat/rhi-5798-sdk-onetimefrom
feature/rhi-6242

Conversation

@highskore

Copy link
Copy Markdown
Contributor

What

Experimental SDK abstraction for settlement-layer session policies (RHI-6242): declare a spend in business terms — tokens, amounts, recipients, target chains, settlement layers — and let the SDK formulate the underlying session-policy combination, instead of hand-wiring arbiter policies per layer.

experimental_defineSpendSession(input) → { session, route, buildBurnOp }:

  • Same-chain → executor route: universal-action / arg-policy scoping on the ERC-20 transfer (recipient allowlist + amount cap) + time-frame.
  • Cross-chain (Across / Eco) → Permit2 route: a claim policy bound to the settlement layer's arbiter, via the existing cross-chain-permit expansion.
  • singleUse installs the one-time-use policy and yields the burn op; omitting it is the explicit ("old way") config.

Capability table + honest refusal

A per-layer capability table (LAYER_CAPABILITIES) records, for each settlement layer, which of {recipient, amount, token, chain} it can actually enforce and whether it's available today. The builder refuses — rather than emitting a restricted-looking but unrestricted session — when a requested layer isn't enforceable yet (the IntentExecutor-backed layers) or can't bind a requested recipient/amount restriction.

IntentExecutor settlement-layer policy encoding

Faithful byte-packed encoders for the IntentExecutor settlement-layer policy family (smart-sessions-v2 #46): the shared base header, ownable multi-layer and static single-layer initData, and the CCTP / Relay / Rhino adapter config blobs — matching the on-chain decode exactly (big-endian, unpadded; raw 20-byte addresses; raw 32-byte CCTP mint recipients). Installed via a new erc1271Policies passthrough on SessionDefinition (resolve drops the default sudo entry so the 1271 list stays a strict AND), guarded against combining with the mutually-exclusive Permit2 claim policy.

The policy has no canonical deployment yet, so its address is required (like oneTimeUseId) and the encoding is validated by byte-exact unit tests rather than e2e.

Why arg-policy can't cover the IntentExecutor layers

For the IntentExecutor-backed layers (Rhino/Relay/CCTP/NEAR/OFT) the bridge deposit is decoded per-execution, so a single call's args are at fixed offsets — CCTP is fully pinnable by arg-policy, Rhino/NEAR partially. But the robust, all-layers answer is the decode-level IntentExecutor policy (adapters), because: the wildcard fallback action would let a settler route around per-action pins; Relay's calldata is opaque; and Rhino-native/NEAR have no on-chain recipient. Details in the RHI-6242 discussion.

Testing

  • Unit: spend-session.test.ts (route selection, scoping, refusal, one-time-use, erc1271 wiring) + settlement-layer.test.ts (byte-exact encoder field placement + size checklist). All green; tsc --noEmit clean.
  • test/integration/scenarios/spend-session.demo.itest.ts — local-fork demo (same-chain settle-once/reject-twice + cross-chain build). On-chain settlement is currently gated by a separate settlement-layer signature issue, tracked independently; the abstraction's formulation is proven by the unit tests.

Notes

Closes RHI-6242

🤖 Generated with Claude Code

highskore and others added 4 commits August 28, 2026 01:42
Declare a spend in business terms — tokens, amounts, recipients, target
chains, settlement layers — and let the SDK formulate the session-policy
combination for the appropriate route:
  - same-chain  → executor route: universal-action / arg-policy scoping on
    the ERC-20 transfer (recipient allowlist + amount cap) + time-frame.
  - cross-chain → permit2 route: a claim policy bound to the settlement
    layer's arbiter, via the existing cross-chain permit expansion.

`singleUse` adds the one-time-use policy and yields the burn op; omitting
it is the explicit ("old way") config. Exposed as
experimental_defineSpendSession from smart-sessions, with unit coverage and
a local-fork demo scenario.

Closes RHI-6242.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-session

Add a per-layer capability table (LAYER_CAPABILITIES) and widen the spend
target's settlementLayers to the full settlement-layer set. defineSpendSession
now refuses — rather than silently emitting a restricted-looking but
unrestricted session — when a requested layer is not enforceable yet (the
IntentExecutor-backed layers, pending the settlement-layer adapter policy) or
cannot bind a requested recipient/amount restriction. Arbiter layers
(SAME_CHAIN/ECO/ACROSS) stay enforceable today via the Permit2 claim policy.

Refs RHI-6242.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… + install path

Add a faithful SDK encoder for the IntentExecutor settlement-layer policy
family (smart-sessions-v2 #46): the shared base header, ownable multi-layer and
static single-layer initData, and the CCTP/Relay/Rhino adapter config blobs —
byte-packed to match the on-chain layout exactly (all scalars big-endian and
unpadded; addresses raw 20 bytes; CCTP mint recipients raw 32 bytes).

Add an `erc1271Policies` passthrough on SessionDefinition (resolve installs them
on the 1271 AND-list and drops the default sudo entry so the list stays strict)
plus an `intentExecutorPolicy` address slot, and wire defineSpendSession to
accept a pre-encoded settlement-layer policy — guarded against combining it with
the mutually-exclusive Permit2 claim policy.

No canonical deployment exists yet, so the address is required (like
oneTimeUseId) and the encoding is validated by byte-exact unit tests.

Refs RHI-6242.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…traction

Consensus fixes from three fresh-context reviews:
- Refuse a fully-unrestricted same-chain spend (no recipients / maxAmount /
  time-window / singleUse) instead of silently emitting a sudo transfer
  allowance — the exact "restricted-looking but unrestricted" outcome the
  builder exists to prevent.
- Require an explicit destination token per target chain (token addresses
  differ per chain — no source-address reuse) and refuse an empty
  `recipients: []` (was fail-open on the cross-chain route).
- Remove the incoherent `route` override (session shape and burn-op route
  could diverge) and the dead, never-read `intentExecutorPolicy` address field.
- Add token/chain enforceability refusals + width guards on the encoder inputs;
  freeze LAYER_CAPABILITIES; use viem `zeroAddress`.
- Tighten tests: real per-chain dest tokens, a pinned (non-tautological)
  layer-id vector, and added empty-recipients / unrestricted-spend /
  missing-dest-token cases. biome clean.

Refs RHI-6242.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@highskore highskore added the show Same as ship; a human eye would be appreciated label Aug 28, 2026

@rhinestone-kevin rhinestone-kevin Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment — adds experimental spend-session and settlement-layer policy helpers; three issues on public-surface packaging and integration test isolation.


Kevin score: 4/10 · impact 4 · economy 0

encodeOneTimeUseIdInitData,
oneTimeUseIdErc1271Policy,
// IntentExecutor settlement-layer policy (experimental — no deployment yet)
addressToBytes32,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

design-smell — AGENTS.md says every fix or feature needs a changeset, and public export additions require a minor changeset; this adds new @rhinestone/sdk/smart-sessions exports without any .changeset/*.md. The release would publish the new API without the required versioned changelog entry.

// Pre-encoded ERC-1271 policy entries to install on the session's 1271 surface
// (e.g. an IntentExecutor settlement-layer policy). Enforcing 1271 policies drop
// the default sudo entry, so the 1271 list stays a strict AND.
erc1271Policies?: ResolvedPolicy[]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

design-smell — This adds the raw 1271 policy field only to the internal domain type; the exported SessionDefinition in src/config/account.ts still lacks erc1271Policies, even though toSession() is typed against that public interface. Consumers can import intentExecutorPolicyEntry() but cannot pass its result to toSession({ erc1271Policies: ... }) without a cast.

process.env.INTEGRATION_ARB_FORK_RPC ?? 'http://localhost:30002'
const API_KEY = process.env.INTEGRATION_RHINESTONE_API_KEY ?? 'testuserapikey'
const POLICY = (process.env.INTEGRATION_ONE_TIME_USE_ID_POLICY ?? '') as Address
if (!POLICY) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

design-smell — This import-time throw makes bun run test:integration fail during file discovery whenever INTEGRATION_ONE_TIME_USE_ID_POLICY is absent. Existing integration env checks happen inside the scenarios that need them, so this demo should skip or fail inside its own tests instead of aborting the whole suite at module load.

- Add the required changeset (minor) for the new experimental exports.
- Surface `erc1271Policies` on the public SessionDefinition so a toSession
  caller can pass an intentExecutorPolicyEntry result without a cast.
- Skip the spend-session demo itest (describe.skipIf) when the policy env var
  is absent, instead of throwing at import and aborting integration discovery.

Refs RHI-6242.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@rhinestone-kevin rhinestone-kevin Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve — adds experimental spend-session and settlement-layer policy helpers; no blockers found.


Kevin score: 4/10 · impact 4 · economy 0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

show Same as ship; a human eye would be appreciated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant